package UI

import (
	"github.com/eiannone/keyboard"
	"github.com/gofiber/fiber/v2/utils"
	"golang.org/x/crypto/ssh/terminal"
	"os"
	"strings"
	"sync"
	"time"
)

// getWindowSize 获取终端窗口大小
// @return 返回值为终端的width、Height
func getWindowSize() (int, int) {
	fd := int(os.Stdout.Fd())
	w, h, err := terminal.GetSize(fd)

	if err != nil {
		panic(GetWindowSizeError + err.Error())
	}
	return w, h
}

// listenWindowSize 持续监听终端串口大小，并触发事件
// @parma fy: 监听的频率，基础单位为1ms
func listenWindowSize(fy int) {
	for true {
		width, height := getWindowSize()
		if (width != SysWidth || height != SysHeight) && Body != nil {
			SysWidth = width
			SysHeight = height
			Render()
			autoSizeChangeTrigger(Body, Body, true)

			time.Sleep(time.Millisecond * time.Duration(fy))
		}

	}
}

// listenKeyBord 持续监听全局键盘事件
func listenKeyBord() {
	err := keyboard.Open()
	if err != nil {
		panic(err)
	}
	defer keyboard.Close()
	for {
		char, key, err := keyboard.GetKey()

		if err != nil {
			panic(err.Error())
		}

		if SelectNode == nil {
			SelectNode = Body
		}

		SelectNode.setKeyBord(keyboard.KeyEvent{
			Key:  key,
			Rune: char,
			Err:  err,
		})

		if SelectNode.isUnMount() || !triggerEvent(SelectNode, OnKeyBord, SelectNode) { //尝试检测节点是否失效，如果失效会进行回退
			backSelect(SelectNode)
		}
	}
}

// Select 选中一个节点作为按键监听的输出节点，此节点必须监听onKeyBord事件，否则将自动回退，直至父节点有监听或Body
// @parma node 选中的节点
func Select(node Node) {
	if node != nil {
		oldSelectNode := SelectNode
		SelectNode = node
		triggerEvent(node, OnSelect, oldSelectNode)
	}
}

// backSelect 尝试光标回退至有效节点
// @parma node 将被回退的节点
func backSelect(node Node) {
	nodeParent, _ := node.GetParent()
	if node != Body && nodeParent != nil {
		if nodeParent.isUnMount() {
			backSelect(nodeParent)
		} else {
			triggerEvent(nodeParent, OnSelect, node)
			SelectNode = nodeParent
		}
	} else if SelectNode != Body && nodeParent == nil {
		SelectNode = Body
	}

}

// Start 初始化服务，使用本框架的必须调用的函数，本函数完成大部分初始化工作
// @parma autoFlash: 是否自动渲染，在自动渲染时对窗口响应时相对及时的 fy: 渲染频率和窗口大小获取频率，基础单位为ms keyBordEvent: 是否开启按键事件监听
func Start(fy uint16, keyBordEvent bool) {
	//确保未初始化
	if isInit {
		return
	}

	// 初始化name分类map
	nameLibrary = make(map[string]NodeStack)

	//初始化Body
	Body = CreateQuadrilateral(BodyName)
	Body.SetVolume(CanvasVolume{ //显示声明供调整的宽高程序才有权利更改
		Width:  Auto,
		Height: Auto,
	})
	Body.SetStyle(CanvasStyle{AutoSize: true, Display: true, ShowText: true, Color: WhiteColor})

	Select(Body)

	if fy <= 0 { //判断刷新频率合法性，如果不合法则使用默认
		fy = SizeReLoadFrequency
	}

	fyParma := int(fy) //转为int类型的参数

	//每x ms查询一次窗口大小
	go listenWindowSize(fyParma)

	if keyBordEvent {
		//持续监听键盘事件
		go listenKeyBord()
	}

	//修改初始化信号
	isInit = true
}

// elementLoop 将一个节点和其所有子节点渲染，使用递归实现
// @parma 将被渲染的node tree
func elementLoop(node Node) {

	if node == Body {
		globalBuf.Reset() //重新渲染时初始化输出文件
	}

	renderResult := renderer(node) //先渲染node，确定node可渲染性
	if !renderResult {             //父组件无法渲染则停止所有子组件的渲染
		return
	}

	zIndexTree := createZIndexTree(ZIndexRenderType)
	child, _ := node.GetChildren()
	zIndexTree.Init(child)
	childNode, ok := zIndexTree.GetNode()
	for ok {
		elementLoop(childNode)
		childNode, ok = zIndexTree.GetNode()
	}
}

// notRenderable 主要用于检测屏幕是否可以被渲染
// @return 在返回值为true的时候，终端不能用于渲染
func notRenderable() bool {
	return SysHeight < 0 || SysWidth < 0
}

// startElementLoop ElementLoop的启动函数，在可渲染的情况下循环调用node
// @parma fy: 循环的频率
// @tips: 废弃函数
func startElementLoop(fy int) {
	for true {
		Render()
		time.Sleep(time.Millisecond * time.Duration(fy))
	}
}

// renderDebounce 渲染防抖函数，同步代码后异步渲染
var renderDebounce = debounce(RenderLazy)

// render 渲染函数
func render() {
	elementLoop(Body)
	print(globalBuf.String())
}

// Render 异步渲染，可手动调用
func Render() {
	if !notRenderable() {
		renderDebounce(render)
	}
}

// debounce 防抖函数
// @parma interval: 防抖时间
// @return 为最终的防抖函数
func debounce(interval time.Duration) func(f func()) {
	var l sync.Mutex
	var timer *time.Timer

	return func(f func()) {
		l.Lock()
		defer l.Unlock()
		// 使用lock保证d.timer更新之前一定先Stop.

		if timer != nil {
			timer.Stop()
		}
		timer = time.AfterFunc(interval, f)
	}
}

// renderer 渲染器，将数据渲染为图形
// @parma node: 将要渲染的node
// @return node渲染的结果，其结果应来自不同的渲染器，所有渲染器都应该为renderer函数返回正确的渲染结果
func renderer(node Node) bool {
	if notRenderable() || node.isUnMount() { //在窗口可视的情况下渲染，组件未销毁的情况下渲染
		return false
	}
	nodeAttr, _ := node.GetAttr()
	nodeStyle, _ := node.GetStyle()
	nodeVolume, _ := node.GetVolume()

	if nodeStyle.Display == false || (nodeStyle.AutoSize == false && (nodeVolume.Width <= 0 || nodeVolume.Height <= 0)) {
		return false
	}

	switch nodeAttr.Tag { //判断node的类型，将node推送给不同的解析器去分类渲染

	case QuadrilateralTag:
		return quadrilateralRender(node.(*Quadrilateral))
	}
	return false
}

// quadrilateralRender 系变形解析渲染
// @parma ql: 四边形struct的指针
// @return 返回该node的渲染结果，如果其不能正常渲染请返回false，这样他的children就不会再解析渲染
func quadrilateralRender(ql *Quadrilateral) bool {

	if (ql.volume.Width <= 0 || ql.volume.Height <= 0) && !ql.style.AutoSize {
		return false
	} else {
		ql.position.totalY = ql.position.Y
		ql.position.totalX = ql.position.X
		if ql.parent != nil {
			pPosition, _ := ql.parent.GetPosition()
			ql.position.totalY += pPosition.totalY
			ql.position.totalX += pPosition.totalX
		}

		return squareDrawing(ql)
	}
}

// CreateQuadrilateral 创建四边形Canvas
// @parma Name: 创建四边形的name，用于标记node，但是不强制唯一
// @return 创建的节点，在节点返回之前他会加载到各个全局仓库中
func CreateQuadrilateral(name string) Node {
	key := utils.UUIDv4()
	element := new(Quadrilateral)
	element.tag = QuadrilateralTag
	element.name = name
	element.key = key
	element.position.ZIndex = 0
	element.position.Y = 0
	element.position.X = 0
	element.unMount = false

	element.style = DefaultStyle

	loadNodeToBase(element)        //将节点加载到分类仓库
	lodeNodeToNameIndex(element)   //将节点加载到以Name字段分类的仓库
	loadNodeToRenderStack(element) //将节点加载到渲染层级仓库
	createEvent(element)           //创建事件

	return element
}

// loadBase 将node加载到对应的全局仓库
// @parma node: 将被加载的node
func loadNodeToBase(node Node) {
	nodeAttr, _ := node.GetAttr()
	switch nodeAttr.Tag {
	case QuadrilateralTag:
		ql, ok := (node).(*Quadrilateral)
		if ok {
			quadrilateralStore.Store(ql.key, ql) //存到全局的变量中,方便渲染器获取信息
		}
		break
	}
}

// loadNodeToRenderStack 将node加载到对应层级的index，在加载的过程中会尝试去重
// @parma node: 将被加载的node
func loadNodeToRenderStack(node Node) {
	position, _ := node.GetPosition()
	attr, _ := node.GetAttr()
	nodeStackAny, ok := zIndexStack.Load(position.ZIndex)
	if ok {
		nodeStack := nodeStackAny.(NodeStack)
		for _, stackNode := range nodeStack {
			snAttr, _ := stackNode.GetAttr()
			if snAttr.Key == attr.Key {
				return
			}
		}
		nodeStack = append(nodeStack, node)
		zIndexStack.Store(position.ZIndex, nodeStack)
	} else {
		zIndexStack.Store(position.ZIndex, NodeStack{node})
	}
}

// delNodeFromRenderStack 删除渲染栈中的某一个Node
// @parma node: 将被删除的node
func delNodeFromRenderStack(node Node) {
	position, _ := node.GetPosition()
	attr, _ := node.GetAttr()

	nodeStackAny, ok := zIndexStack.Load(position.ZIndex)

	if ok {
		nodeStack := nodeStackAny.(NodeStack)
		for index, stackNode := range nodeStack {
			snAttr, _ := stackNode.GetAttr()
			if snAttr.Key == attr.Key {
				newNodeStack := append(nodeStack[:index], nodeStack[index+1:]...) //删除元素
				zIndexStack.Store(position.ZIndex, newNodeStack)
				break
			}
		}
	}
}

// getElementFromBase 在全局的仓库中查找某一个element
// @parma Key: element的key nodeType: element的类型，可以输入"*"再所有仓库中尝试获取
// @return 返回Node类型，节点的详细类型需要自己调用GetAttr去判断，这种做法是返回统一的类型
func getElementFromBase(key string) (Node, bool) {
	node, ok := getQuadrilateralElementFromBase(key)
	if ok {
		return node, ok
	}

	return node, ok
}

// delNodeFromBase 将node在对应的全局仓库删除
// @parma node: 将被删除的node
func delNodeFromBase(node Node) {
	nodeAttr, _ := node.GetAttr()
	switch nodeAttr.Tag {
	case QuadrilateralTag:
		ql, ok := (node).(*Quadrilateral)
		if ok {
			quadrilateralStore.Delete(ql.key) //存到全局的变量中,方便渲染器获取信息
		}
		break
	}
}

// getQuadrilateralElementFromBase 在四边形仓库中查找一个element
// @parma Key: 要查找的key
// @return 查找的结果，是否成功
func getQuadrilateralElementFromBase(key string) (*Quadrilateral, bool) {
	value, ok := quadrilateralStore.Load(key)
	ql, ok := value.(*Quadrilateral)
	return ql, ok
}

// lodeNodeToNameIndex 将element以其name字段进行分组存储
// @parma node: 将被加载的node
// @return 加载的结果
func lodeNodeToNameIndex(node Node) bool {

	attr, _ := node.GetAttr()
	base, ok := nameLibrary[attr.Name]
	if ok {
		for _, value := range base { //去重
			snAttr, _ := value.GetAttr()
			if snAttr.Key == attr.Key {
				return false
			}
		}
		nameLibrary[attr.Name] = append(base, node)
	} else {
		nameLibrary[attr.Name] = NodeStack{node}
	}
	return true
}

// delNodeFromNameIndex 将element以其name字段进行删除
// @parma node: 将被删除的node
func delNodeFromNameIndex(node Node) {
	attr, _ := node.GetAttr()
	base, ok := nameLibrary[attr.Name]
	if ok {
		for index, value := range base {
			snAttr, _ := value.GetAttr()
			if snAttr.Key == attr.Key {
				nameLibrary[attr.Name] = append(base[:index], base[index+1:]...)
			}
		}
	}

}

// GetNodeByName 尝试获取一个Node通过Name
// @parma Name: node.Name
// @return 获取到的 node stack，和获取的结果
func GetNodeByName(name string) (NodeStack, bool) {
	nameIndex, ok := nameLibrary[name]
	return nameIndex, ok
}

// createZIndexTree 尝试创建一个层级树，允许选择不同类型的层级树，如果超出所支持的类型，则默认RenderTree
// @parma typeType: 绘画树的类型
// @return 返回值为一个层级树
func createZIndexTree(treeType uint8) PaintingTree {
	switch treeType {
	case ZIndexRenderType:
		return new(RenderZIndexTree)
	default:
		return new(RenderZIndexTree)
	}
}

// throw 用于内部抛出错误
// @parma 错误信息
func throw(msg string) {
	str := strings.Builder{}
	str.WriteString("<<")
	str.WriteString(PackageName)
	str.WriteString(">>")
	str.WriteByte(' ')
	str.WriteString(msg)

	panic(str.String())
}

// Remove 用于批量销毁节点
func Remove(nodes ...Node) {
	for _, node := range nodes {
		node.Remove() //无论是否销毁都去尝试
	}
}
